# $Id: ipv6.py 9227e81e919a 2012/08/27 15:49:57 pgurumur $
# Copyright (c) 2009 Prabhu Gurumurthy  <pgurumur@gmail.com>
# 
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#

__version__  = "$Revision: 9227e81e919a $"
__author__   = "$Author: pgurumur $"
__modified__ = "$Date: 2012-08-27 15:49:57Z $"

# essential imports
import hashlib
import math
import random
import time

# Local imports
import ip
import stdlib
import exception

from exception import RFCError

# Simple lambda function, returning a random number
getrand = lambda number: random.randint(1, number)

# Get digest for the following authentication algorithms
sha256 = lambda string: hashlib.sha256(string).hexdigest()
md5 = lambda string: hashlib.md5(string).hexdigest()

# Generate MAC address randomly
def GetMac(Length = 48):
   if Length == 48:
      value = 11
   elif Length == 64:
      value = 15

   tempstring = ""
   count = 0
   while (count < 3):
      count += 1
      try:
         tempstring += sha256(stdlib.GetRandomString(getrand(75)))
      except AssertionError, message:
         pass

   mac = "0"
   for ch in tempstring[:value]:
      length = len(mac) + 1
      if (length % 3) == 0:
         mac += ":"

      mac += ch

   # return the mac address
   return mac

# RFC4193 class
# Snippet of rfc 4193, section 3.1
#
#      | 7 bits |1|  40 bits   |  16 bits  |          64 bits           |
#      +--------+-+------------+-----------+----------------------------+
#      | Prefix |L| Global ID  | Subnet ID |        Interface ID        |
#      +--------+-+------------+-----------+----------------------------+
#
#   Where:
#
#      Prefix            FC00::/7 prefix to identify Local IPv6 unicast
#                        addresses.
#
#      L                 Set to 1 if the prefix is locally assigned.
#                        Set to 0 may be defined in the future.  See
#                        Section 3.2 for additional information.
#
#      Global ID         40-bit global identifier used to create a
#                        globally unique prefix.  See Section 3.2 for
#                        additional information.
#
#      Subnet ID         16-bit Subnet ID is an identifier of a subnet
#                        within the site.
#
#      Interface ID      64-bit Interface ID as defined in [ADDARCH].
# XXX: This class does not strictly adhere to the RFC4193 spec
class rfc4193(object):
   def __init__(self, Length, Prefix = False):
      super(rfc4193, self).__init__()

      # Min and max values to generate the IPv6 address
      self._ipv6MaxLen = 128
      self._ipv6MinLen = 12

      self._length = 0
      self._prefix = False

      self._ip = None

      try:
         self._length = int(Length)
      except ValueError as err:
         raise RFCError(err)

      self._prefix = Prefix

   def __str__(self):
      return str(self._ip)

   def __repr__(self):
      return repr(str(self))

   def __unicode__(self):
      return unicode(str(self))

   # Stir up the hash based on mac addres, random number, ntp time or
   # local time and sha value of the random
   def GetHash(self):
      randstr = None
      retval = None
      tmp = 200
      mac = GetMac()
      try:
         if randstr:
            randstr += stdlib.GetRandomString(getrand(25)) + mac
         else:
            randstr = stdlib.GetRandomString(getrand(25)) + mac

         randstr += stdlib.GetRandomString(getrand(25))
         randstr += sha256(randstr) + stdlib.GetRandomString(getrand(25))
      except TypeError, message:
         pass
      except AssertionError, message:
         pass

      randstr += "%0.05f" %time.time()
      if randstr:
         retval = sha256(randstr) + md5(randstr)

      return retval

   # Function that returns ip module, if prefix option is used
   def GetIP(self):
      retval = None
      if self._prefix:
         retval = self._ip

      return retval

   ip = property(fget = lambda self: self.GetIP())

   def GetIPv6(self):
      # What is my return value?
      retval = ""

      # Starting index value
      index = 1

      # Starting index for prefix
      rfc4193 = "fd"

      hashval = self.GetHash()

      # Starting prefix length 
      prefixLen = (self._length / 4)

      if (self._length % 4):
         raise RFCError("prefix length must be divisible by 4")

      if self._prefix:
         prefixLen -= 2

      # How many zero should I prefix or suffix when the length is not
      # divisible 16
      howmany = int(math.floor((self._length % 16) / 4))

      # iterate through hash string
      for ix in hashval:
         if index <= prefixLen:
            if retval:
               retval += ix
            else:
               if self._prefix:
                  retval = rfc4193 + ix
               else:
                  # the following if/elif loops are for determining, when to
                  # prepend leading zeros for IPv6 node addresses
                  if howmany == 1:
                     retval += "000" + ix
                  elif howmany == 2:
                     retval += "00" + ix
                  elif howmany == 3:
                     retval += "0" + ix
                  else:
                     retval = ix

            value = 0
            if self._prefix:
               value = index + 2
            else:
               value = index - howmany

            fourth = value % 4
            # Add the colon only after the fourth character
            if ((fourth == 0) and (index < prefixLen)):
               retval += ":"

            index += 1

      if self._prefix:
         # the following if/elif loops are for determining when to prepend
         # leading zeroes for IPV6 prefixes
         if howmany == 1:
            retval += "000"
         elif howmany == 2:
            retval += "00"
         elif howmany == 3:
            retval += "0"

         if self._length < self._ipv6MaxLen:
            retval += "::/%d" %self._length
         else:
            retval += "/%d" %self._length

         try:
            self._ip  = ip.ip(retval)
         except exception.IPError as err:
            raise RFCError(err)
         else:
            retval = self._ip

      return retval
